/**
 * Copyright 2011 Renato de Jesus Manzoni
 * 
 * SED - Software Engineering and Dependability Research Group
 * 
 *     http://www.sed.ic.unicamp.br/
 * 
 * Distributed Systems Laboratory (LSD) in the Institute of Computing (IC) at University of Campinas (UNICAMP)
 * 
 *     http://www.lsd.ic.unicamp.br/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package br.unicamp.ic.lsd.wscaa.concurrent;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import br.unicamp.ic.lsd.wscaa.WSCAManager;
import br.unicamp.ic.lsd.wscaa.exception.WSDynamicException;

/**
 * @author Renato de Jesus Manzoni <rjmanzoni@gmail.com>
 * 
 */
public class ConcurrentExceptionSync {
    private Queue<WSDynamicException> queue = new LinkedList<WSDynamicException>();
    private long timeWindowResolution;
    private TimeUnit unit;
    private boolean doneExceptionsHandling;
    private static final long TIME_WINDOW_RESOLUTION = 40L;
    private final Lock lock = new ReentrantLock();

    private static ConcurrentExceptionSync instance = new ConcurrentExceptionSync(
	    TIME_WINDOW_RESOLUTION, TimeUnit.MILLISECONDS);

    private static Log logger = LogFactory
	    .getLog(ConcurrentExceptionSync.class);

    // private List<Object> waitResolution;

    int k = 0;

    public static ConcurrentExceptionSync getInstance() {

	return instance;

    }

    public void init(long timeWindowResolution, TimeUnit unit) {

	this.timeWindowResolution = timeWindowResolution;

	this.unit = unit;

	// waitResolution = new ArrayList<Object>();
    }

    public void refresh() {

	if (queue == null)
	    queue = new LinkedList<WSDynamicException>();
	else {
	    if (queue.size() > 0)
		throw new RuntimeException(
			"There are exceptions to be handling!!!");
	    queue.clear();
	}

	this.doneExceptionsHandling = true;

    }

    private ConcurrentExceptionSync(long timeWindowResolution, TimeUnit unit) {

	init(timeWindowResolution, unit);

	refresh();

    }

    public synchronized void addException(WSDynamicException e) {

	queue.add(e);

	new Thread() {
	    public void run() {
		syncException();
	    }
	}.start();
    }

    // TODO definir um listener quando chega uma excecao?
    private void syncException() {
	List<WSDynamicException> exceptions = null;
	if (lock.tryLock()) {
	    if (logger.isTraceEnabled())
		logger.trace("Thread lock...");
	    try {
		doneExceptionsHandling = false;
		// espera x mili segundos para os restantes das excecoes...
		// try {
		// Thread.sleep(timeWindowResolution);
		// } catch (InterruptedException e) {
		// e.printStackTrace();
		// }
		// Thread.currentThread().
		synchronized (this) {
		    try {
			unit.timedWait(this, timeWindowResolution);
		    } catch (InterruptedException e) {
			e.printStackTrace();
		    }

		}

		exceptions = new ArrayList<WSDynamicException>();

		// synchronized (exceptions) {
		while (queue.size() > 0)
		    exceptions.add(queue.remove());
		// }
	    } finally {
		if (logger.isTraceEnabled())
		    logger.trace("Thread unlock...");
		lock.unlock();
	    }
	}
	// TODO: deveria ser sincrono ou assincrono o metodo abaixo???
	if (exceptions != null && !exceptions.isEmpty())
	    handleExceptions(exceptions);

    }

    // TODO: acho que essa classe nao deveria ser o handler
    private void handleExceptions(List<WSDynamicException> exceptions) {

	if (logger.isInfoEnabled())
	    logger.info("Handling the exceptions...");

	// FIXME: precisa passar um conjunto de exceptions para encontrar a
	// raiz
	// @SuppressWarnings("unused")
	// WSDynamicException e = resolutionException();

	for (Iterator<WSDynamicException> iterator = exceptions.iterator(); iterator
		.hasNext();) {

	    Exception exception = iterator.next();

	    if (logger.isDebugEnabled())
		logger.debug("Exceptions = " + exception);
	}

	try {
	    Thread.sleep(5000);
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
	logger.info("Handling the exceptions OK");

	doneExceptionsHandling = true;
	wakeUpThreads();

    }

    public boolean hasDoneExceptionHandling() {

	return doneExceptionsHandling;
    }

    @SuppressWarnings("unused")
    private WSDynamicException resolutionException() {
	throw new RuntimeException("Method not implemented");
    }

    private void wakeUpThreads() {
	WSCAManager.getInstance().notifyObjects();
	// for (Iterator<Object> iterator = waitResolution.iterator(); iterator
	// .hasNext();) {
	// Object type = iterator.next();
	// synchronized (type) {
	// type.notifyAll();
	// }
	//
	// }

    }

    // public void addWait(Object obj) {
    // waitResolution.add(obj);
    // }

    // @Override
    // public void handleException(WSDynamicException e) {
    //
    // if (logger.isInfoEnabled())
    // logger.info("Adding exception be handler...");
    //
    // addException(e);
    //
    // }

}
